
package trim.differentmaterials.core;

import java.util.*;
import trim.common.*;
import trim.differentmaterials.generator.columns.*;
import trim.differentmaterials.generator.core.*;
import trim.common.inputdata.*;
import trim.common.result.*;

/**
 *
 * @author Podorov Alexander
 */

public class TrimCore
{
    public static final double DEFAULT_EPSILON = 1E-11;
    public static final double DEFAULT_FUNCTION_VALUE = 1E-8;

    private final int materialCount;
    private final int formatCount;
    
    private final MaterialStock[] materialStock;
    private final FormatOrder[] formatOrder;
    
    private double functionValue;
    private double estimation;
    private final double[] direction;
    private final double[] variables;
    private final double[] dualVariables;
    private final double[][] inverseMatrix;

    private final List<Column> patterns;
    private Column currentPattern;
    private int leftIndex;

    private final int[] rightPart;
    private final int[] functionCoefficients;

    private final int[] materialRests;
    private final int[] formatRests;

    private boolean isArtificialMode;

    private Generator generator;

    public TrimCore(InputData inputData)
    {
        List<Integer> wrongMaterials = new ArrayList<Integer>();

        int firstMaterialCount = inputData.getMaterialStockList().size();
        formatCount = inputData.getFormatOrderList().size();

        int[] materialLengths = new int[firstMaterialCount];
        int[] formatLengths = new int[formatCount];

        for (int i = 0; i < firstMaterialCount; i++)
        {
            materialLengths[i] = inputData.getMaterialStockList().get(i).getMaterial().getLength();
        }

        for (int i = 0; i < formatCount; i++)
        {
            formatLengths[i] = inputData.getFormatOrderList().get(i).getFormat().getLength();
        }

        for (int i = 0; i < firstMaterialCount; i++)
        {
            boolean isSmallMaterial = true;

            for (int j = 0; j < formatCount; j++)
            {
                if (formatLengths[j] <= materialLengths[i])
                {
                    isSmallMaterial = false;
                    break;
                }
            }

            if (isSmallMaterial)
            {
                wrongMaterials.add(i);
            }
        }

        materialCount = firstMaterialCount - wrongMaterials.size();

        materialStock = new MaterialStock[materialCount];

        int next = 0;
        for (int i = 0; i < firstMaterialCount; i++)
        {
            if (!wrongMaterials.contains(i))
            {
                materialStock[next] = inputData.getMaterialStockList().get(i);
                next++;
            }
        }

        formatOrder = inputData.getFormatOrderList().toArray(new FormatOrder[0]);
        
        functionValue = 0.0;
        estimation = 0.0;
        direction = new double[materialCount + formatCount];
        variables = new double[materialCount + formatCount];
        dualVariables = new double[materialCount + formatCount];
        inverseMatrix = new double[materialCount + formatCount][materialCount + formatCount];

        patterns = new ArrayList<Column>();

        rightPart = new int[materialCount + formatCount];
        functionCoefficients = new int[materialCount + formatCount];

        materialRests = new int[materialCount];
        formatRests = new int[formatCount];
    }

    public void setInitializer(TrimCoreInitializer initializer)
    {
        initializer.initialize();
        computeVariables();
        computeCoefficients();
        computeDualVariables();
        computeFunctionValue();
    }

    public void setGenerator(Generator generator)
    {
        this.generator = generator;
    }

    public void setRestTrimmer(RestTrimmer restTrimmer) throws NoSolutionsException
    {
        restTrimmer.start();
    }

    private void computeCoefficients()
    {
        isArtificialMode = false;

        for (Column colomn : patterns)
        {
            if (colomn.getClass() == ArtificialColomn.class)
            {
                isArtificialMode = true;
                break;
            }
        }

        for (int i = 0; i < patterns.size(); i++)
        {
            Column pattern = patterns.get(i);
            int coefficient = pattern.getCoefficient().getValue(isArtificialMode);
            
            if (pattern.getClass() == RealPattern.class)
            {
                functionCoefficients[i] = coefficient;
                continue;
            }
            else
            {
                int position = ((Gap)(patterns.get(i))).getPosition();
                
                if (pattern.getClass() == ArtificialColomn.class)
                {
                    functionCoefficients[i] = coefficient;
                    continue;
                }
                if (pattern.getClass() == PositiveMaterialGap.class)
                {
                    functionCoefficients[i] = coefficient * materialStock[position].getMaterial().getLength();
                    continue;
                }
                if (pattern.getClass() == NegativeMaterialGap.class)
                {
                    functionCoefficients[i] = coefficient * materialStock[position].getMaterial().getLength();
                    continue;
                }
                if (pattern.getClass() == PositiveFormatGap.class)
                {
                    functionCoefficients[i] = coefficient * formatOrder[position].getFormat().getLength();
                    continue;
                }
                if (pattern.getClass() == NegativeFormatGap.class)
                {
                    functionCoefficients[i] = coefficient * formatOrder[position].getFormat().getLength();
                    continue;
                }
            }
        }
    }

    private void computeVariables()
    {
        for (int i = 0; i < materialCount + formatCount; i++)
        {
            double value = 0.0;
            for (int j = 0; j < materialCount + formatCount; j++)
            {
                value += inverseMatrix[j][i] * rightPart[j];
            }
            variables[i] = value;
        }
    }

    private void computeDualVariables()
    {
        for (int i = 0; i < materialCount + formatCount; i++)
        {
            double value = 0.0;
            for (int j = 0; j < materialCount + formatCount; j++)
            {
                value += functionCoefficients[j] * inverseMatrix[i][j];
            }
            dualVariables[i] = value;
        }
    }

    private void computeFunctionValue()
    {
        functionValue = 0.0;
        for (int i = 0; i < materialCount + formatCount; i++)
        {
            functionValue += functionCoefficients[i] * variables[i];
        }
    }

    public void startSymplexMethod() throws NoSolutionsException
    {
        if (isArtificialMode)
        {
            generator.setArtificialMode(true);

            setDirectionAndEstimation();
            while (estimation > DEFAULT_EPSILON)
            {
                setLeftIndex();
                replaceInPatterns();
                toNewBasis();
                setDirectionAndEstimation();
            }

            if (functionValue > DEFAULT_FUNCTION_VALUE)
            {
                throw new NoSolutionsException("No possible solutions");
            }

            computeCoefficients();
            computeDualVariables();
            computeFunctionValue();
        }

        generator.setArtificialMode(false);

        setDirectionAndEstimation();
        while (estimation > DEFAULT_EPSILON)
        {
            setLeftIndex();
            replaceInPatterns();
            toNewBasis();
            setDirectionAndEstimation();
        }
    }

    private void setDirectionAndEstimation()
    {
        currentPattern = generator.getSymplexMethodPattern();
        estimation = currentPattern.getEstimation();
        int[] items = currentPattern.getItems();

        for (int i = 0; i < materialCount + formatCount; i++)
        {
            double value = 0.0;
            for (int j = 0; j < materialCount + formatCount; j++)
            {
                value += inverseMatrix[j][i] * items[j];
            }
            direction[i] = value;
        }
    }

    private void setLeftIndex()
    {
        double min = Double.MAX_VALUE;
        for (int i = 0; i < materialCount + formatCount; i++)
        {
            if ((direction[i] > DEFAULT_EPSILON) && (min > variables[i] / direction[i]))
            {
                min = variables[i] / direction[i];
                leftIndex = i;
            }
        }
    }

    private void replaceInPatterns()
    {
        patterns.set(leftIndex, currentPattern);
    }

    private void toNewBasis()
    {
        functionValue -= variables[leftIndex] * estimation / direction[leftIndex];
        for (int i = 0; i < materialCount + formatCount; i++)
        {
            dualVariables[i] -= inverseMatrix[i][leftIndex] * estimation / direction[leftIndex];
        }
        for (int i = 0; i < materialCount + formatCount; i++)
        {
            if (i != leftIndex)
            {
                variables[i] -= variables[leftIndex] * direction[i] / direction[leftIndex];
            }
        }
        for (int i = 0; i < materialCount + formatCount; i++)
        {
            for (int j = 0; j < materialCount + formatCount; j++)
            {
                if (j != leftIndex)
                {
                    inverseMatrix[i][j] -= inverseMatrix[i][leftIndex] * direction[j] / direction[leftIndex];
                }
            }
        }
        variables[leftIndex] /= direction[leftIndex];
        for (int i = 0; i < materialCount + formatCount; i++)
        {
            inverseMatrix[i][leftIndex] /= direction[leftIndex];
        }
    }

    public void normalizePatterns()
    {
        for (int i = 0; i < materialCount + formatCount; i++)
        {
            if (patterns.get(i).getClass() == RealPattern.class)
            {
                RealPattern pattern = (RealPattern)patterns.get(i);                
                pattern.setCount((int)(variables[i] + DEFAULT_EPSILON));              
            }            
        }

        boolean flag = true;

        while (flag)
        {
            flag = false;
            for (int i = 0; i < patterns.size(); i++)
            {
                if (patterns.get(i).getClass() != RealPattern.class)
                {
                    patterns.remove(i);
                    flag = true;
                }
                else
                {
                    RealPattern pattern = (RealPattern)patterns.get(i);

                    if (pattern.getCount() == 0)
                    {
                        patterns.remove(i);
                        flag = true;
                    }
                }
            }
        }
    }

    public void comparePatterns()
    {
        boolean flag = true;

        while (flag)
        {
            flag = false;
            for (int i = 0; i < patterns.size(); i++)
            {
                for (int j = 0; j < patterns.size(); j++)
                {
                    RealPattern pattern1 = (RealPattern)patterns.get(i);
                    RealPattern pattern2 = (RealPattern)patterns.get(j);

                    if ((i != j) && (pattern1.equals(pattern2)))
                    {
                        pattern1.setCount(pattern1.getCount() + pattern2.getCount());
                        patterns.remove(j);
                        flag = true;
                    }
                }
            }
        }
    }

    public TrimResult getTrimResult()
    {
        TrimResult trimResult = new TrimResult();

        for (int i = 0; i < patterns.size(); i++)
        {
            Pattern trimResultPattern = new Pattern();
            for (int j = 0; j < formatCount; j++)
            {
                int[] items = patterns.get(i).getItems();
                
                for (int k = 0; k < items[j]; k++)
                {
                    trimResultPattern.add(new PatternItem(new Format(formatOrder[j].getFormat())));
                }
            }
            RealPattern pattern = (RealPattern)patterns.get(i);

            trimResultPattern.setCount(pattern.getCount());
            trimResultPattern.setMaterial(new Material(pattern.getMaterial()));
            trimResult.add(trimResultPattern);
        }
        return trimResult;
    }

    public int getMaterialCount()
    {
        return materialCount;
    }

    public int getFormatCount()
    {
        return formatCount;
    }

    public MaterialStock[] getMaterialStock()
    {
        return materialStock;
    }

    public FormatOrder[] getFormatOrder()
    {
        return formatOrder;
    }
    
    public double[] getDualVariables()
    {
        return dualVariables;
    }

    public double[][] getInverseMatrix()
    {
        return inverseMatrix;
    }

    public List<Column> getPatterns()
    {
        return patterns;
    }

    public int[] getRightPart()
    {
        return rightPart;
    }   

    public int[] getMaterialRests()
    {
        return materialRests;
    }

    public int[] getFormatRests()
    {
        return formatRests;
    }
}
